/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ru.dusty.fs.filesystem.utils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import ru.dusty.fs.filesystem.data.Block;
import ru.dusty.fs.filesystem.data.BlockType;
import ru.dusty.fs.filesystem.data.BlocksMap;
import ru.dusty.fs.filesystem.data.MetaData;
import ru.dusty.fs.filesystem.data.SuperBlock;
import ru.dusty.fs.filesystem.exceptions.FileAlreadyExistsException;

/**
 *
 * @author vadim
 */
public class Utils {
    
    public static final int BLOCK_MIN_SIZE = 1024;
    
    public static final int FS_MAX_SIZE = 50 * BLOCK_MIN_SIZE * BLOCK_MIN_SIZE;
    
    public static final int BLOCK_MIN_COUNT = 1;
    
    public static final String ROOT_NAME = "root";
    
    private static SuperBlock superBlock = null;
    
    private static void loadSuperBlock(String root) throws FileNotFoundException, IOException {
        if (superBlock != null) return;
        
        Block firstBlock = new Block(new File(root + File.separator + "1"));
        superBlock = new SuperBlock();
        firstBlock.readMetaData(superBlock);
    }
    
    public static boolean isFsFormatted(String root) throws FileNotFoundException, IOException {
        loadSuperBlock(root);
        
        return (superBlock.getName().equals(ROOT_NAME));
    }
    
    public static List<String> parsePath(String path) {
        LinkedList<String> list = new LinkedList<String>();
        
        File file = new File(path);
        if (!file.getName().isEmpty() && !file.getName().equals(".")) 
            list.add(file.getName());
        String parent = file.getParent();
        while (parent != null) {
            file = new File(parent);
            if (!file.getName().isEmpty() && !file.getName().equals("."))
                list.addFirst(file.getName());
            parent = file.getParent();
        }
        
        return list;
    }
    
    public static String listToPath(List<String> list) {
        StringBuilder str = new StringBuilder();
        
        for (String val: list) {
            str.append(File.separator);
            str.append(val);
        }
        
        return str.toString();
    }
    
    public static Block getFirstBlock(String root, List<String> path) throws FileNotFoundException, IOException {
        Block block = new Block(new File(root + File.separator + "1"));
        MetaData metaData = new SuperBlock();
        block.readMetaData(metaData);
        metaData.setName("");
        
        for (int i = 0; i < path.size(); i++) {
            if (metaData.getType().equals(BlockType.FILE)) {
                if (i != path.size() - 1) return null;
                
                if (metaData.getName().equals(path.get(i))) {
                    return block;
                } else {
                    return null;
                }
            } else {
                int filesCount = metaData.getSize();
                Block tempBlock = null;
                block.seek(metaData.getMetaDataSize());
                int nextBlock;
                do {
                    nextBlock = block.getNextBlock();
                    while (filesCount != 0 && !metaData.getName().equals(path.get(i)) 
                            && block.hasMoreInt()) {
                        int val = block.read();
                        if (val != 0) {
                            filesCount--;
                            if (tempBlock != null) tempBlock.close();
                            tempBlock = new Block(new File(root + File.separator + val));
                            if (metaData instanceof SuperBlock) metaData = new MetaData();
                            tempBlock.readMetaData(metaData);
                        }
                    }

                    if (nextBlock != 0 && filesCount != 0 && !metaData.getName().equals(path.get(i))) {
                        block = new Block(new File(root + File.separator + nextBlock));

                        block.seek(0);
                    }
                } while (nextBlock != 0 && filesCount != 0 && !metaData.getName().equals(path.get(i)));
                
                if (metaData.getName().equals(path.get(i))) {
                    block.close();
                    block = tempBlock;
                } else {
                    return null;
                }
            }
        }
        
        if (path.isEmpty()) return block;
        
        if (metaData.getName().equals(path.get(path.size() - 1))) return block;
        else return null;
    }
    
    public static List<Integer> getContainedBlocks(String root, Block block)
            throws FileNotFoundException, IOException {
        LinkedList<Integer> list = new LinkedList<Integer>();
        
        MetaData metaData;
        if (block.getFileName().equals("1")) {
            metaData = new SuperBlock();
        } else {
            metaData = new MetaData();
        }

        block.readMetaData(metaData);
        int filesCount = metaData.getSize();
        block.seek(metaData.getMetaDataSize());
        int nextBlock;
        do {
            nextBlock = block.getNextBlock();
            while (filesCount != 0 && block.hasMoreInt()) {
                int val = block.read();
                if (val != 0) {
                    filesCount--;
                    list.add(val);
                }
            }

            if (nextBlock != 0 && filesCount != 0) {
                block = new Block(new File(root + File.separator + nextBlock), "r");

                block.seek(0);
            }
        } while (nextBlock != 0 && filesCount != 0);
        
        return list;
    }
    
    public static void addToDir(String root, BlocksMap map,
            Block firstBlock, int num) throws FileNotFoundException, OutOfMemoryError, IOException {
        MetaData data;
        if (firstBlock.getFileName().equals("1")) {
            data = new SuperBlock();
        } else {
            data = new MetaData();
        }
        
        firstBlock.seek(data.getMetaDataSize());
        int val = -1;
        Block block = firstBlock;
        boolean flag = true;
        while (val != 0 && flag) {
            if (block.hasMoreInt()) {
                val = block.read();
            } else {
                int nextBlock = block.getNextBlock();
                flag = (nextBlock != 0);
                if (flag) {
                    block = new Block(new File(root + File.separator + nextBlock));
                    block.seek(0);
                }
            }
        }
        
        if (val != 0) {
            int nextNum = map.allocateBlock();
            if (nextNum == -1) throw new OutOfMemoryError("Not enough memory blocks.");
            
            Block nextBlock = new Block(new File(root + File.separator + nextNum));
            nextBlock.clear();
            block.setNextBlock(nextNum);
            nextBlock.seek(0);
            nextBlock.write(num);
        } else {
            block.seek(block.pos() - PrimitiveSizes.sizeof(num));
            block.write(num);
        }
        
        firstBlock.readMetaData(data);
        data.setLastUpdDate(new Date());
        data.setSize(data.getSize() + 1);
        firstBlock.writeMetaData(data);
    }
    
    public static void createDir(String root, BlocksMap map,
            List<String> path) throws FileNotFoundException, FileAlreadyExistsException,
            OutOfMemoryError, IOException {
        List<String> list = new LinkedList<String>();
        Block parentBlock = new Block(new File(root + File.separator + "1"));
        
        for (int i = 0; i < path.size(); i++) {
            list.add(path.get(i));
            Block tempBlock = getFirstBlock(root, list);
            if (tempBlock == null) {
                MetaData data = new MetaData();
                parentBlock.readMetaData(data);
                if (data.getType().equals(BlockType.FILE)) 
                    throw new FileAlreadyExistsException(data.getName());
                
                for (int n = i; n < path.size(); n++) {
                    MetaData dirData = new MetaData();
                    dirData.setName(path.get(n));
                    dirData.setType(BlockType.FOLDER);
                    
                    Block block = createElemInDir(root, map, parentBlock);
//                    int blockNum = map.allocateBlock();
//                    if (blockNum == -1) throw new OutOfMemoryError("Not enough memory blocks.");
//                    Block block = new Block(new File(root + File.separator + blockNum));
                    block.clear();
                    block.writeMetaData(dirData);
                    
                    //addToDir(root, map, parentBlock, blockNum);
                    parentBlock = block;
                }
                return;
            } else {
                MetaData data = new MetaData();
                tempBlock.readMetaData(data);
                if (data.getType().equals(BlockType.FILE))
                    throw new FileAlreadyExistsException(data.getName());
                
                parentBlock = tempBlock;
            }
        }
    }
    
    private static void removeFromDir(String root, Block firstBlock, int num) throws FileNotFoundException, IOException {
        MetaData data;
        if (firstBlock.getFileName().equals("1")) {
            data = new SuperBlock();
        } else {
            data = new MetaData();
        }
        
        firstBlock.seek(data.getMetaDataSize());
        int val = -1;
        Block block = firstBlock;
        boolean flag = true;
        while (val != num && flag) {
            if (block.hasMoreInt()) {
                val = block.read();
            } else {
                int nextBlock = block.getNextBlock();
                flag = (nextBlock != 0);
                if (flag) {
                    block = new Block(new File(root + File.separator + nextBlock));
                    block.seek(0);
                }
            }
        }
        
        if (val != num) return;
        
        block.seek(block.pos() - PrimitiveSizes.sizeof(num));
        block.write(0);
        
        firstBlock.readMetaData(data);
        data.setLastUpdDate(new Date());
        data.setSize(data.getSize() - 1);
        firstBlock.writeMetaData(data);
    }
    
//    public static void remove(String root, BlocksMap map, List<String> path) throws FileNotFoundException, IOException {
//        remove(root, path);
//        //map.initMap();
//    }
    
    public static void remove(String root, List<String> path) throws FileNotFoundException, IOException {
        Block block = getFirstBlock(root, path);
        
        if (block == null) return;
        
        MetaData data = new MetaData();
        block.readMetaData(data);
        
        if (block.getFileName().equals("1")) {
            List<Integer> list = getContainedBlocks(root, block);
            for (Integer blockNum: list) {
                Block tempBlock = new Block(new File(root + File.separator + blockNum));
                tempBlock.readMetaData(data);
                tempBlock.close();
                path.add(data.getName());
                remove(root, path);
                path.remove(path.size() - 1);
            }
        } else {
            String name = path.get(path.size() - 1);
            path.remove(path.size() - 1);
            Block parentBlock = getFirstBlock(root, path);
            path.add(name);
            
            if (parentBlock == null) return;
            
            parentBlock.readMetaData(data);
            if (data.getType().equals(BlockType.FILE)) return;
            
            removeFromDir(root, parentBlock, Integer.parseInt(block.getFileName()));
        }
    }
    
    public static Block createElemInDir(String root, BlocksMap map, Block parentBlock) 
            throws FileNotFoundException, OutOfMemoryError, IOException {
        int blockNum = map.allocateBlock();
        if (blockNum == -1) {
            throw new OutOfMemoryError("Not enough memory blocks.");
        }
        Block block = new Block(new File(root + File.separator + blockNum));
        block.setNextBlock(0);
        
        addToDir(root, map, parentBlock, blockNum);
        
        return block;
    }
    
    private static void clearFile(String root, BlocksMap map, Block firstBlock) throws FileNotFoundException, IOException {
        MetaData data = new MetaData();
        firstBlock.readMetaData(data);
        
        data.setSize(0);
        firstBlock.writeMetaData(data);

        int nextBlock = firstBlock.getNextBlock();
        while (nextBlock != 0) {
            Block tempBlock = new Block(new File(root + File.separator + nextBlock));
            map.deallocateBlock(nextBlock);
            nextBlock = tempBlock.getNextBlock();
        }
        firstBlock.setNextBlock(0);
    }
    
    private static void copyFile(String root, BlocksMap map, Block srcBlock, Block dstBlock, MetaData dstData)
            throws OutOfMemoryError, FileNotFoundException, IOException {
        MetaData srcData = new MetaData();
        srcBlock.readMetaData(srcData);
        
        int srcSize = srcData.getSize();
        int sizeInBlock = getBlockSize(root) - PrimitiveSizes.sizeof(srcSize);
        Block firstBlock = dstBlock;
        Block prevBlock = dstBlock;
        byte[] bytes = new byte[sizeInBlock];
        int nextBlock = -1;
        while (nextBlock != 0) {
            srcBlock.seek(0);
            srcBlock.readData(bytes, 0, sizeInBlock);
            
            dstBlock.seek(0);
            dstBlock.writeData(bytes, 0, sizeInBlock);
            
            nextBlock = srcBlock.getNextBlock();
            if (nextBlock != 0) {
                srcBlock = new Block(new File(root + File.separator + nextBlock));
                
                int blockNum = map.allocateBlock();
                if (blockNum == -1) {
                    throw new OutOfMemoryError("Not enough memory blocks.");
                }
                dstBlock = new Block(new File(root + File.separator + blockNum));
                prevBlock.setNextBlock(blockNum);
                
                prevBlock = dstBlock;
            }
        }
        dstBlock.setNextBlock(0);
        
        dstData.setLastUpdDate(new Date());
        dstData.setSize(srcSize);
        firstBlock.writeMetaData(dstData);
    }
    
    private static void copyFile(String root, BlocksMap map, Block srcBlock,
            List<String> dstPath) throws FileNotFoundException, FileAlreadyExistsException,
            OutOfMemoryError, IOException {
        Block block = getFirstBlock(root, dstPath);
        
        MetaData srcData = new MetaData();
        srcBlock.readMetaData(srcData);
        
        MetaData data;
        if (block == null) {
            String fileName = dstPath.get(dstPath.size() - 1);
            dstPath.remove(dstPath.size() - 1);
            createDir(root, map, dstPath);
            Block parentBlock = getFirstBlock(root, dstPath);
            dstPath.add(fileName);

            data = new MetaData();
            data.setName(fileName);
            data.setType(BlockType.FILE);

            block = createElemInDir(root, map, parentBlock);
            block.writeMetaData(data);
        } else {
            data = new MetaData();
            block.readMetaData(data);
            
            if (data.getType().equals(BlockType.FOLDER)) {
                Block parentBlock = block;
                block = createElemInDir(root, map, parentBlock);
                data = new MetaData();
                data.setName(srcData.getName());
                data.setType(BlockType.FILE);
                block.writeMetaData(data);
            } else {
                clearFile(root, map, block);
                
//                data.setSize(0);
//                block.writeMetaData(data);
//
//                int nextBlock = block.getNextBlock();
//                while (nextBlock != 0) {
//                    Block tempBlock = new Block(new File(root + File.separator + nextBlock));
//                    map.deallocateBlock(nextBlock);
//                    nextBlock = tempBlock.getNextBlock();
//                }
//                block.setNextBlock(0);
            } 
        }
        
//        int srcSize = srcData.getSize();
//        int sizeInBlock = getBlockSize(root) - PrimitiveSizes.sizeof(srcSize);
//        Block firstBlock = block;
//        Block prevBlock = block;
//        byte[] bytes = new byte[sizeInBlock];
//        int nextBlock = -1;
//        while (nextBlock != 0) {
//            srcBlock.seek(0);
//            srcBlock.readData(bytes, 0, sizeInBlock);
//            
//            block.seek(0);
//            block.writeData(bytes, 0, sizeInBlock);
//            
//            nextBlock = srcBlock.getNextBlock();
//            if (nextBlock != 0) {
//                srcBlock = new Block(new File(root + File.separator + nextBlock));
//                
//                int blockNum = map.allocateBlock();
//                if (blockNum == -1) {
//                    throw new OutOfMemoryError("Not enough memory blocks.");
//                }
//                block = new Block(new File(root + File.separator + blockNum));
//                prevBlock.setNextBlock(blockNum);
//                
//                prevBlock = block;
//            }
//        }
//        block.setNextBlock(0);
//        
//        data.setLastUpdDate(new Date());
//        data.setSize(srcSize);
//        firstBlock.writeMetaData(data);
        copyFile(root, map, srcBlock, block, data);
    }
    
    private static Map<String, Block> dirNames(String root, Block block) throws IOException {
        Map<String, Block> result = new HashMap<String, Block>();
        List<Integer> nums = getContainedBlocks(root, block);
        
        for (Integer num: nums) {
            Block tempBlock = new Block(new File(root + File.separator + num));
            MetaData data = new MetaData();
            tempBlock.readMetaData(data);
            result.put(data.getName(), tempBlock);
        }
        
        return result;
    }
    
    private static void copyDir(String root, BlocksMap map, Block srcBlock, Block dstBlock) 
            throws FileNotFoundException, IOException {
        if (srcBlock.getFileName().equals(dstBlock.getFileName())) return;
        
        MetaData dstData = new MetaData();
        dstBlock.readMetaData(dstData);
        
        if (dstData.getType().equals(BlockType.FILE)) {
            System.out.println(dstData.getName() + ": not a folder.");
            System.out.println("Missed copying the appropriate folder.");
            return;
        }
        
        MetaData srcData = new MetaData();
        srcBlock.readMetaData(srcData);
        
        Map<String, Block> names = dirNames(root, dstBlock);
        String name = srcData.getName();
        if (srcData.getType().equals(BlockType.FILE)) {
            Block block;
            if (names.containsKey(name)) {
                block = names.get(name);
                block.readMetaData(dstData);
                if (dstData.getType().equals(BlockType.FOLDER)) {
                    System.out.println(dstData.getName() + ": not a file.");
                    System.out.println("Missed copying the appropriate file.");
                    return;
                }
                
                clearFile(root, map, block);
            } else {
                block = createElemInDir(root, map, dstBlock);
                dstData = new MetaData();
                dstData.setName(name);
                dstData.setType(BlockType.FILE);
            }

            copyFile(root, map, srcBlock, block, dstData);
        } else {
            Block block;
            if (names.containsKey(name)) {
                block = names.get(name);
                block.readMetaData(dstData);
                if (dstData.getType().equals(BlockType.FILE)) return;
                
                if (block.getFileName().equals(srcBlock.getFileName())) return;
            } else {
                block = createElemInDir(root, map, dstBlock);
                srcData.setSize(0);
                srcData.setLastUpdDate(new Date());
                block.writeMetaData(srcData);
            }
            
            List<Integer> nums = getContainedBlocks(root, srcBlock);
            for (Integer num : nums) {
                Block tempBlock = new Block(new File(root + File.separator + num));
                copyDir(root, map, tempBlock, block);
            }
        }
    }
    
    public static void copy(String root, BlocksMap map, Block srcBlock,
            List<String> dstPath) throws FileNotFoundException, FileAlreadyExistsException,
            OutOfMemoryError, IOException {
        
        MetaData data = new MetaData();
        srcBlock.readMetaData(data);
        
        if (data.getType().equals(BlockType.FILE)) {
            copyFile(root, map, srcBlock, dstPath);
        } else {
            Block dstBlock = getFirstBlock(root, dstPath);
            
            if (dstBlock == null) {
                createDir(root, map, dstPath);
                dstBlock = getFirstBlock(root, dstPath);
            } else {
                MetaData dstData = new MetaData();
                dstBlock.readMetaData(dstData);
                if (dstData.getType().equals(BlockType.FILE)) {
                    throw new FileAlreadyExistsException(listToPath(dstPath));
                }
            }
            
            copyDir(root, map, srcBlock, dstBlock);
        }
    }
    
    public static void copy(String root, BlocksMap map, List<String> srcPath,
            List<String> dstPath) throws FileNotFoundException, FileAlreadyExistsException,
            OutOfMemoryError, IOException {
        Block block = getFirstBlock(root, srcPath);
        copy(root, map, block, dstPath);
    }
    
    public static int getBlocksCount(String root) throws FileNotFoundException, IOException {
        loadSuperBlock(root);
        
        return superBlock.getBlockCount();
    }
    
    public static int getBlockSize(String root) throws FileNotFoundException, IOException {
        loadSuperBlock(root);
        
        return superBlock.getBlockSize();
    } 
    
    public static boolean isCorrectPathToCopy(List<String> srcPath, List<String> dstPath) {
        if (srcPath.isEmpty()) return false;
        
        int i = 0;
        while (i < srcPath.size() && i < dstPath.size()) {
            if (!srcPath.get(i).equals(dstPath.get(i))) return true;
            i++;
        }
        
        return (srcPath.size() > dstPath.size());
    }
}
